package com.zh.demo04.config;

import com.zh.demo04.login.WebUserDetailsService;
import com.zh.demo04.verifycode.VerifyCode;
import com.zh.demo04.verifycode.VerifyCodeService;
import com.zh.demo04.verifycode.sms.SmsCode;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Objects;


/**
 * 手机号登录校验Filter
 * 请求接口需要携带参数 phone,verifyCode
 */
@Slf4j
@AllArgsConstructor
public class PhoneLoginAuthFilter extends OncePerRequestFilter {
    /**
     * 请求时 验证码参数的name
     */
    static final String PARAM_VERIFY_CODE = "verifyCode";
    static final String PARAM_PHONE = "phone";

    /**
     * 验证码生成抽象类
     */
    private VerifyCodeService verifyCodeService;
    private WebUserDetailsService webUserDetailsService;
    private AuthenticationFailureHandler authenticationFailureHandler;
    private AuthenticationSuccessHandler authenticationSuccessHandler;

    @Override
    final protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        var uri = request.getRequestURI();
        System.out.println("into PhoneLoginAuthFilter ... uri = " + uri);

        if (isLoginUrl(request)) {
            try {
                checkVerifyCode(request);
            } catch (AuthenticationException ex) {
                log.warn("手机验证码 校验未通过。 {}", ex.getMessage());
                authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
                return;
            }

            var phone  =request.getParameter(PARAM_PHONE);
            try{
                var userPrincipal = webUserDetailsService.loadUserByUsername(phone);

                //新生成的authenticationToken已经是isAuthenticated的了
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userPrincipal, null, userPrincipal.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                authenticationToken.eraseCredentials();
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                log.info("手机号登录 认证通过 phone = {}", phone);

                authenticationSuccessHandler.onAuthenticationSuccess(request, response, authenticationToken);
                return;
            }catch (AuthenticationException ex){
                log.warn("手机号登录 未通过。 {}", ex.getMessage());
                authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
                return;
            }

        }

        filterChain.doFilter(request, response);
    }

    /**
     * 是不是手机号登录接口
     * 当请求url是指定需要检查的，而且必须是POST方式，才执行检查验证码操作
     *
     * @param request
     * @return
     */
    private boolean isLoginUrl(HttpServletRequest request) {
        if (!"POST".equalsIgnoreCase(request.getMethod()))
            return false;

        var requestURI = request.getRequestURI();

        return "/api/phone/login".equalsIgnoreCase(requestURI);
    }

    /**
     * 进行验证码的校验
     *
     * @param request
     */
    private void checkVerifyCode(HttpServletRequest request) {
        //获取客户端的提交的验证码value 和 服务端的验证码对象
        String codeValue = request.getParameter(PARAM_VERIFY_CODE);
        String phoneValue = request.getParameter(PARAM_PHONE);

        VerifyCode code = verifyCodeService.getVerificationCode(request);
        if (code instanceof SmsCode smsCode){
            if (!Objects.equals(smsCode.getPhone(), phoneValue))
                throw new BadCredentialsException("验证码和手机号不匹配");
        }
        verifyCodeService.validate(request, codeValue);

        log.debug("验证码对比成功：codeValue = {}", codeValue);
    }
}
